/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "fxVSIncludes.fx"

struct LIGHT_PS
{
	float4 Position: SV_POSITION;
	float2 TexCoord: TEXCOORD;
	float3 LightVector: LIGHTVECTOR;
	float3 Normal: NORMAL;
};

struct SHADOWED_PS
{
	float4 Position: SV_POSITION;
	float2 TexCoord: TEXCOORD;
	float3 LightVector: LIGHTVECTOR;
	float3 ShadowCoord: SHADOWCOORD;
	float3 Normal: NORMAL;
};

struct SHADOW_GS
{
	float4 Position : SV_POSITION;
};

struct SHADOW_PS
{
	float4 Position : SV_POSITION;
	float3 LightVector: LIGHTVECTOR;
	uint RTIndex : SV_RenderTargetArrayIndex;
};

struct BOUNDS_VS
{
	float3 Origin : ORIGIN;
	float Radius : RADIUS;
	float4 Position : POSITION;
};

struct DEFERRED_PS
{
	float4 Position: SV_POSITION;
	float2 TexCoord: TEXCOORD;
	float3 Normal: NORMAL;
	float3 WorldPositon : WORLDPOSITION;
};


// split off because it's shared with the geometry shader
LIGHT_PS CommonLightVS (float4 Position, float2 TexCoord, float3 Normal)
{
	LIGHT_PS vs_out;

	vs_out.Position = mul (EntityMatrix, mul (LocalMatrix, Position));
	vs_out.TexCoord = TexCoord;
	vs_out.LightVector = NewLightOrigin - mul (LocalMatrix, Position).xyz;
	vs_out.Normal = mul ((float3x3) LocalMatrix, Normal);

	return vs_out;
}


// split off because it's shared with the geometry shader
SHADOWED_PS CommonShadowedVS (float4 Position, float2 TexCoord, float3 Normal)
{
	SHADOWED_PS vs_out;

	vs_out.Position = mul (EntityMatrix, mul (LocalMatrix, Position));
	vs_out.TexCoord = TexCoord;
	vs_out.LightVector = NewLightOrigin - mul (LocalMatrix, Position).xyz;

	float4 WorldPos = mul (LocalMatrix, Position);

	// i need to flip it in z because right now i'm drawing my cubemap upside-down... :(
	vs_out.ShadowCoord = float3 (1, 1, -1) * -vs_out.LightVector;

	vs_out.Normal = mul ((float3x3) LocalMatrix, Normal);

	return vs_out;
}

DEFERRED_PS CommonDeferredVS (float4 Position, float2 TexCoord, float3 Normal)
{
	DEFERRED_PS vs_out;

	// transform the deferred pass the same way as the shadow volume pass otherwise we get z-fighting
	// note - this breaks viewmodels-poking-into-walls
	vs_out.Position = mul (EntityMatrix, mul (LocalMatrix, Position));
	vs_out.TexCoord = TexCoord;
	vs_out.Normal = mul ((float3x3) LocalMatrix, Normal);
	vs_out.WorldPositon = mul (LocalMatrix, Position).xyz;

	return vs_out;
}

#ifdef VERTEXSHADER
float4 DeferredQuadVS (uint VertexID : SV_VERTEXID) : SV_POSITION
{
	return QuadPositions[VertexID];
}


float4 DeferredIcosVS (float4 Position : POSITION) : SV_POSITION
{
	// scale and position the light volume to the light
	// (http://en.wikipedia.org/wiki/Icosahedron - sphere radius relative to icosahedron size)
	float4x4 VolumeMatrix = float4x4 (
		float4 (NewLightRadius * 1.33f, 0, 0, NewLightOrigin.x),
		float4 (0, NewLightRadius * 1.33f, 0, NewLightOrigin.y),
		float4 (0, 0, NewLightRadius * 1.33f, NewLightOrigin.z),
		float4 (0, 0, 0, 1)
	);

	return mul (WorldMatrix, mul (VolumeMatrix, Position));
}

float4 DeferredIcosVS666 (uint VertexID : SV_VERTEXID) : SV_POSITION
{
	// scale and position the light volume to the light
	// (http://en.wikipedia.org/wiki/Icosahedron - sphere radius relative to icosahedron size)
	float4x4 VolumeMatrix = float4x4 (
		float4 (NewLightRadius * 1.33f, 0, 0, NewLightOrigin.x),
		float4 (0, NewLightRadius * 1.33f, 0, NewLightOrigin.y),
		float4 (0, 0, NewLightRadius * 1.33f, NewLightOrigin.z),
		float4 (0, 0, 0, 1)
	);

	return mul (WorldMatrix, mul (VolumeMatrix, IcosPositions[VertexID]));
}


SURF_VS SurfDeferredVS (SURF_VS vs_in)
{
	// just pass-through because we need a GS to generate normals for the surface
	return vs_in;
}


DEFERRED_PS MeshDeferredVS (MESH_VS vs_in)
{
	return CommonDeferredVS (
		lerp (vs_in.Position0, vs_in.Position1, lerpBlend),
		vs_in.TexCoord,
		lerp (vs_in.Normal0, vs_in.Normal1, lerpBlend)
	);
}


float4 LightBoundsVS (BOUNDS_VS vs_in) : SV_POSITION
{
	// scale and position the light volume to the light
	// (http://en.wikipedia.org/wiki/Icosahedron - sphere radius relative to icosahedron size)
	float4x4 VolumeMatrix = float4x4 (
		float4 (vs_in.Radius * 1.33f, 0, 0, vs_in.Origin.x),
		float4 (0, vs_in.Radius * 1.33f, 0, vs_in.Origin.y),
		float4 (0, 0, vs_in.Radius * 1.33f, vs_in.Origin.z),
		float4 (0, 0, 0, 1)
	);

	return mul (WorldMatrix, mul (VolumeMatrix, vs_in.Position));
}

float4 SurfShadowVS (SURF_VS vs_in) : WORLDSPACEPOSITION
{
	return mul (LocalMatrix, vs_in.Position);
}

float4 MeshShadowVS (MESH_VS vs_in) : WORLDSPACEPOSITION
{
	return mul (LocalMatrix, lerp (vs_in.Position0, vs_in.Position1, lerpBlend));
}

float4 MeshShadowVolumeVS (MESHSV_VS vs_in) : WORLDSPACEPOSITION
{
	return mul (LocalMatrix, lerp (vs_in.Position0, vs_in.Position1, lerpBlend));
}


SURF_VS SurfLightVS (SURF_VS vs_in)
{
	return vs_in;
}

LIGHT_PS MeshLightVS (MESH_VS vs_in)
{
	return CommonLightVS (
		lerp (vs_in.Position0, vs_in.Position1, lerpBlend),
		vs_in.TexCoord,
		lerp (vs_in.Normal0, vs_in.Normal1, lerpBlend)
	);
}

SURF_VS SurfShadowedVS (SURF_VS vs_in)
{
	return vs_in;
}

SHADOWED_PS MeshShadowedVS (MESH_VS vs_in)
{
	return CommonShadowedVS (
		lerp (vs_in.Position0, vs_in.Position1, lerpBlend),
		vs_in.TexCoord,
		lerp (vs_in.Normal0, vs_in.Normal1, lerpBlend)
	);
}
#endif


#ifdef GEOMETRYSHADER
[maxvertexcount(3)]
void SurfDeferredGS (triangle SURF_VS gs_in[3], inout TriangleStream<DEFERRED_PS> triStream)
{
	// duplicate Quake's fucked-up normals (QBSP didn't normalize this)
	float3 normal = cross (gs_in[0].Position.xyz - gs_in[1].Position.xyz, gs_in[2].Position.xyz - gs_in[1].Position.xyz);

	// and output our triangle
	triStream.Append (CommonDeferredVS (gs_in[0].Position, gs_in[0].TexCoord, normal));
	triStream.Append (CommonDeferredVS (gs_in[1].Position, gs_in[1].TexCoord, normal));
	triStream.Append (CommonDeferredVS (gs_in[2].Position, gs_in[2].TexCoord, normal));
	triStream.RestartStrip ();
}

[maxvertexcount(3)]
void SurfLightGS (triangle SURF_VS gs_in[3], inout TriangleStream<LIGHT_PS> triStream)
{
	// duplicate Quake's fucked-up normals (QBSP didn't normalize this)
	float3 normal = cross (gs_in[0].Position.xyz - gs_in[1].Position.xyz, gs_in[2].Position.xyz - gs_in[1].Position.xyz);

	// and output our triangle
	triStream.Append (CommonLightVS (gs_in[0].Position, gs_in[0].TexCoord, normal));
	triStream.Append (CommonLightVS (gs_in[1].Position, gs_in[1].TexCoord, normal));
	triStream.Append (CommonLightVS (gs_in[2].Position, gs_in[2].TexCoord, normal));
	triStream.RestartStrip ();
}

[maxvertexcount(3)]
void SurfShadowedGS (triangle SURF_VS gs_in[3], inout TriangleStream<SHADOWED_PS> triStream)
{
	// duplicate Quake's fucked-up normals (QBSP didn't normalize this)
	float3 normal = cross (gs_in[0].Position.xyz - gs_in[1].Position.xyz, gs_in[2].Position.xyz - gs_in[1].Position.xyz);

	// and output our triangle
	triStream.Append (CommonShadowedVS (gs_in[0].Position, gs_in[0].TexCoord, normal));
	triStream.Append (CommonShadowedVS (gs_in[1].Position, gs_in[1].TexCoord, normal));
	triStream.Append (CommonShadowedVS (gs_in[2].Position, gs_in[2].TexCoord, normal));
	triStream.RestartStrip ();
}


SHADOW_GS ShadowGSOut (float4 pos)
{
	SHADOW_GS gs_out;
	gs_out.Position = mul (WorldMatrix, pos);
	return gs_out;
}


[maxvertexcount(18)]
void ShadowVolumeGS (triangleadj float4 gs_in[6] : WORLDSPACEPOSITION, inout TriangleStream<SHADOW_GS> triStream)
{
	float3 ns[3]; // Normals
	float3 d[3]; // Directions toward light
	float4 v[4]; // Temporary vertices

	// Triangle oriented toward light source
	float4 or_pos[3] = {gs_in[0], gs_in[2], gs_in[4]};

	// Compute normal at each vertex.
	ns[0] = cross (gs_in[2].xyz - gs_in[0].xyz, gs_in[4].xyz - gs_in[0].xyz);
	ns[1] = cross (gs_in[4].xyz - gs_in[2].xyz, gs_in[0].xyz - gs_in[2].xyz);
	ns[2] = cross (gs_in[0].xyz - gs_in[4].xyz, gs_in[2].xyz - gs_in[4].xyz);

	// Compute direction from vertices to light.
	d[0] = NewLightOrigin - gs_in[0].xyz;
	d[1] = NewLightOrigin - gs_in[2].xyz;
	d[2] = NewLightOrigin - gs_in[4].xyz;

	// Check if the main triangle faces the light.
	bool faces_light = true;

	if (!(dot (ns[0], d[0]) > 0 || dot (ns[1], d[1]) > 0 || dot (ns[2], d[2]) > 0))
	{
		// Flip vertex winding order in or_pos.
		or_pos[1] = gs_in[4];
		or_pos[2] = gs_in[2];
		faces_light = false;
		//return;
	}

	// Render caps. This is only needed for z-fail.
	if (NewLightZPass < 0)
	{
		// offset front verts by an epsilon to prevent them z-fighting with the model
		triStream.Append (ShadowGSOut (or_pos[0]));
		triStream.Append (ShadowGSOut (or_pos[1]));
		triStream.Append (ShadowGSOut (or_pos[2]));
		triStream.RestartStrip ();

		// Far cap: extrude positions to infinity.
		triStream.Append (ShadowGSOut (float4 (or_pos[0].xyz - NewLightOrigin, 0)));
		triStream.Append (ShadowGSOut (float4 (or_pos[2].xyz - NewLightOrigin, 0)));
		triStream.Append (ShadowGSOut (float4 (or_pos[1].xyz - NewLightOrigin, 0)));
		triStream.RestartStrip ();
	}

	// Loop over all edges and extrude if needed.
	for (uint i = 0; i < 3; i++)
	{
		// Compute indices of neighbor triangle.
		uint v0 = i * 2;
		uint nb = (i * 2 + 1);
		uint v1 = (i * 2 + 2) % 6;

		// Compute normals at vertices, the *exact*
		// same way as done above!
		ns[0] = cross (gs_in[nb].xyz - gs_in[v0].xyz, gs_in[v1].xyz - gs_in[v0].xyz);
		ns[1] = cross (gs_in[v1].xyz - gs_in[nb].xyz, gs_in[v0].xyz - gs_in[nb].xyz);
		ns[2] = cross (gs_in[v0].xyz - gs_in[v1].xyz, gs_in[nb].xyz - gs_in[v1].xyz);

		// Compute direction to light, again as above.
		d[0] = NewLightOrigin - gs_in[v0].xyz;
		d[1] = NewLightOrigin - gs_in[nb].xyz;
		d[2] = NewLightOrigin - gs_in[v1].xyz;

		// Extrude the edge if it does not have a
		// neighbor, or if it's a possible silhouette.
		if (gs_in[nb].w < 0.1f || (faces_light != (dot (ns[0], d[0]) > 0 || dot (ns[1], d[1]) > 0 || dot (ns[2], d[2]) > 0)))
		{
			// Make sure sides are oriented correctly. 
			int i0 = faces_light ? v0 : v1;
			int i1 = faces_light ? v1 : v0;

			triStream.Append (ShadowGSOut (gs_in[i0]));
			triStream.Append (ShadowGSOut (float4 (gs_in[i0].xyz - NewLightOrigin, 0)));
			triStream.Append (ShadowGSOut (gs_in[i1]));
			triStream.Append (ShadowGSOut (float4 (gs_in[i1].xyz - NewLightOrigin, 0)));
			triStream.RestartStrip ();
		} 
	}
}


[maxvertexcount(18)]
void ObjectShadowGS (triangle float4 gs_in[3] : WORLDSPACEPOSITION, inout TriangleStream<SHADOW_PS> CubeMapStream)
{
	for (int f = 0; f < 6; f++)
	{
		SHADOW_PS gs_out;

		gs_out.RTIndex = f;

		float4 pos[3] = {
			mul (FaceMatrix[f], gs_in[0]),
			mul (FaceMatrix[f], gs_in[1]),
			mul (FaceMatrix[f], gs_in[2])
		};

		// Use frustum culling to improve performance
		float4 t0 = saturate (pos[0].xyxy * float4 (-1, -1, 1, 1) - pos[0].w);
		float4 t1 = saturate (pos[1].xyxy * float4 (-1, -1, 1, 1) - pos[1].w);
		float4 t2 = saturate (pos[2].xyxy * float4 (-1, -1, 1, 1) - pos[2].w);
		float4 t = t0 * t1 * t2;

		[branch]
		if (!any (t))
		{
			// Use backface culling to improve performance
			float2 d0 = pos[1].xy * pos[0].w - pos[0].xy * pos[1].w;
			float2 d1 = pos[2].xy * pos[0].w - pos[0].xy * pos[2].w;

			[branch]
			if (d1.x * d0.y > d0.x * d1.y || min (min (pos[0].w, pos[1].w), pos[2].w) < 0.0)
			{
				[unroll]
				for (int v = 0; v < 3; v++)
				{
					gs_out.Position = pos[v];
					gs_out.LightVector = NewLightOrigin - gs_in[v].xyz;
					CubeMapStream.Append (gs_out);
				}

				CubeMapStream.RestartStrip ();
			}
		}
	}
}
#endif


#ifdef PIXELSHADER
struct DEFERRED_OUT
{
	float4 Diffuse : SV_TARGET0;
	float3 Normal : SV_TARGET1;
	float3 Position : SV_TARGET2;
};

DEFERRED_OUT SurfDeferredPS (DEFERRED_PS ps_in)
{
	DEFERRED_OUT ps_out;

	ps_out.Diffuse = surfTexture.Sample (surfSampler, ps_in.TexCoord);
	ps_out.Normal = normalize (ps_in.Normal);
	ps_out.Position = ps_in.WorldPositon.xyz;

	return ps_out;
}


float4 CommonLightPS (LIGHT_PS ps_in) : SV_TARGET0
{
	float4 diff = surfTexture.Sample (surfSampler, ps_in.TexCoord);
	float Angle = ((dot (normalize (ps_in.Normal), normalize (ps_in.LightVector)) * 0.5f) + 0.5f) / 256.0f;
	float Add = max ((NewLightRadius - length (ps_in.LightVector)) * Angle, 0.0f);

	return float4 (diff.rgb * NewLightColour * Add, 0.0f);
}


float4 CommonShadowedPS (SHADOWED_PS ps_in) : SV_TARGET0
{
/*
	float3 LightNormal = normalize (ps_in.LightVector);

	// check spotlight falloff
	float SpotDot = dot (NewSpotVector, LightNormal);
	float AdjustedFalloff = NewLightFalloff * 0.9f;

	clip (AdjustedFalloff - SpotDot);

	// build a smoother falloff
	float SpotShade = 1.0f;

	// just set falloff stupidly high for non-spotlights
	if (NewLightFalloff < 10000.0f)
		SpotShade = smoothstep (AdjustedFalloff, NewLightFalloff, SpotDot);

	// evaluate distance to light and discard if it's too far away
	float Dist = length (ps_in.LightVector);
	clip (NewLightRadius - Dist);

	float4 diff = surfTexture.Sample (surfSampler, ps_in.TexCoord);

	float Angle = ((dot (normalize (ps_in.Normal), LightNormal) * 0.5f) + 0.5f) / 256.0f;
	float Add = max ((NewLightRadius - Dist) * Angle, 0.0f);

	float Depth = Dist * NewLightDepthBias;

	float Shadow = ShadowCube.SampleLevel (lmapSampler, float3 (1, 1, -1) * -ps_in.LightVector, 0).x;
		//ps_in.ShadowCoord, 0).x;

	Shadow = saturate (2.0f - exp (Depth - Shadow));

	return float4 (diff.rgb * NewLightColour * Add * Shadow * SpotShade, 0.0f);
*/
	return 1;
}

float4 ObjectShadowPS (SHADOW_PS ps_in) : SV_TARGET0
{
	// store positive or negative on alternate shadow frames
	return length (ps_in.LightVector);
}

Texture2D DiffuseBuffer : register(t14);
Texture2D NormalsBuffer : register(t15);
Texture2D PositionBuffer : register(t16);
Texture2D LightBuffer : register(t17);
TextureCube ShadowCube : register(t18);


float4 DeferredCommonPS (float4 Position : SV_POSITION) : SV_TARGET0
{
	// take a world-space texcoord
	int3 TexCoord = int3 (Position.xy, 0);

	// load the world position first so that we can early-out if we need to
	float3 WorldPos = PositionBuffer.Load (TexCoord).xyz;

	// calculate a vector to the light
	float3 LightVector = NewLightOrigin - WorldPos;
	float3 LightNormal = normalize (LightVector);

	// evaluate distance to light
	float Dist = length (LightVector);

	// ignore lights too far away
	clip (NewLightRadius - Dist);

	// load normal
	float3 Normal = normalize (NormalsBuffer.Load (TexCoord).rgb);

	// ignore lights behind the surface (biasing it a little to make up for fp imprecision and lack of <=)
	clip (dot (LightVector, Normal) - 0.001f);

	// evaluate shadow depth
	//float Depth = Dist * NewLightDepthBias;

	// ignore parts of the scene in shadow (if a trickframe was on this returns it to positive)
	//clip (ShadowCube.SampleLevel (lmapSampler, float3 (1, 1, -1) * -LightVector, 0).x - Depth);

	// check spotlight falloff
	float SpotDot = dot (NewSpotVector, LightNormal);
	float AdjustedFalloff = NewLightFalloff * 0.9f;

	// ignore if out of range of the spot
	clip (AdjustedFalloff - SpotDot);

	// build a smoother falloff
	float SpotShade = 1.0f;

	// just set falloff stupidly high for non-spotlights
	if (NewLightFalloff < 10000.0f)
		SpotShade = smoothstep (AdjustedFalloff, NewLightFalloff, SpotDot);

	// load the gbuffer
	float3 Diffuse = DiffuseBuffer.Load (TexCoord).rgb;

	// calculate attenuation
	float Angle = ((dot (Normal, LightNormal) * 0.5f) + 0.5f) / 256.0f;
	float Add = (NewLightRadius - Dist) * Angle;

	// light.exe did this
	clip (Add);

	// return the shaded pixel
	return float4 (Diffuse.rgb * NewLightColour * Add * SpotShade, 0.0f);
}
#endif

